package com.corpgovernment.common.currency;

import cn.hutool.core.bean.BeanUtil;
import com.corpgovernment.common.base.JSONResult;
import com.corpgovernment.common.currency.bo.CurrencyBO;
import com.corpgovernment.common.currency.bo.CurrencyExchangeBO;
import com.corpgovernment.common.currency.bo.ExchangeInfoBo;
import com.corpgovernment.common.currency.bo.ExchangeTokenBO;
import com.corpgovernment.common.currency.client.BasicManageCurrencyClient;
import com.corpgovernment.common.currency.client.dto.req.CurrencyExchangeReqDTO;
import com.corpgovernment.common.currency.client.dto.resp.CurrencyExchangeRespDTO;
import com.corpgovernment.common.currency.client.dto.resp.CurrencyRespDTO;
import com.corpgovernment.common.currency.client.dto.resp.ExchangeTokenRespDTO;
import com.corpgovernment.common.currency.client.dto.resp.GetExchangeInfoRespVo;
import com.corpgovernment.common.currency.context.SpringContext;
import com.ctrip.corp.obt.generic.core.context.TraceContext;
import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 汇率转换工具
 *
 * @author ldma
 * @date 2023/8/21
 */
@Slf4j
public class CurrencyConversionUtil {

    private static final Cache<String, ExchangeInfoBo> EXCHANGE_TOKEN_CACHE = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build();

    /**
     * 刷新汇率
     */
    public static void updateExchangeRate() {
        log.info("开始刷新汇率");
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<String> result = currencyClient.updateExchangeRate();
        if(result == null || result.getData() == null) {
            log.error("刷新汇率失败 result={}", result);
            return;
        }
        log.info("刷新汇率成功");
    }

    /**
     * 刷新货币信息缓存
     */
    public static void updateCurrencyRuleCache() {
        log.info("开始刷新货币信息");
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<String> result = currencyClient.updateCurrencyRuleCache();
        if(result == null || result.getData() == null) {
            log.error("刷新货币信息失败 result={}", result);
            return;
        }
        log.info("刷新货币信息成功");
    }

    /**
     * 获取支持的币种
     */
    public static List<CurrencyBO> listSupportCurrency() {
        log.info("开始获取系统支持的货币");
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<List<CurrencyRespDTO>> result = currencyClient.listSupportCurrency();
        if(result == null || result.getData() == null) {
            log.error("获取系统支持的货币失败 result={}", result);
            return null;
        }
        List<CurrencyBO> currencyBOList = result.getData().stream().map(item -> new CurrencyBO(item.getCode(), item.getCnName())).collect(Collectors.toList());
        log.info("获取系统支持的货币成功 result={} currencyBOList={}", result, currencyBOList);
        return currencyBOList;
    }

    /**
     * 交换货币
     */
    public static CurrencyExchangeBO exchangeCurrency(String fromCurrencyCode, String toCurrencyCode, BigDecimal fromCurrencyAmount) {
        log.info("开始转换货币 fromCurrencyCode={} toCurrencyCode={} fromCurrencyAmount={}", fromCurrencyCode, toCurrencyCode, fromCurrencyAmount);
        if (StringUtils.isBlank(fromCurrencyCode) || StringUtils.isBlank(toCurrencyCode) || fromCurrencyAmount == null) {
            log.error("输入参数错误");
            return null;
        }
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<CurrencyExchangeRespDTO> result = currencyClient.exchangeCurrency(new CurrencyExchangeReqDTO(fromCurrencyCode, toCurrencyCode, fromCurrencyAmount));
        if(result == null || result.getData() == null) {
            log.error("转换货币失败 result={}", result);
            return null;
        }
        log.info("转换货币成功 result={}", result.getData());
        return convert(result.getData());
    }

    /**
     * 获取token（旧版）
     */
    public static ExchangeTokenBO getExchangeToken(String exchangeToken) {
        log.info("开始获取token exchangeToken={}", exchangeToken);
        if (StringUtils.isBlank(exchangeToken)) {
            log.error("输入参数错误");
            return null;
        }
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<ExchangeTokenRespDTO> result = currencyClient.getExchangeToken(exchangeToken);
        if(result == null || result.getData() == null) {
            log.error("获取token失败 result={}", result);
            return null;
        }
        log.info("获取token成功 result={}", result.getData());
        return convert(result.getData());
    }

    /**
     * 获取token信息（新版）
     */
    public static ExchangeInfoBo getExchangeInfo(String exchangeToken) {
        log.info("开始获取token（新版） exchangeToken={}", exchangeToken);
        if (StringUtils.isBlank(exchangeToken)) {
            log.error("输入参数错误");
            return null;
        }
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<ExchangeTokenRespDTO> result = currencyClient.getExchangeToken(exchangeToken);
        if(result == null || result.getData() == null) {
            log.error("获取token（新版）失败 result={}", result);
            return null;
        }
        ExchangeTokenRespDTO exchangeTokenRespDTO = result.getData();
        log.info("获取token（新版）成功 exchangeTokenRespDTO={}", exchangeTokenRespDTO);
        return convert(exchangeTokenRespDTO, exchangeToken);
    }

    /**
     * 货币转换（新版）
     */
    public static ExchangeInfoBo exchange(String fromCurrencyCode, String toCurrencyCode, BigDecimal fromCurrencyAmount) {
        log.info("开始货币转换（新版）fromCurrencyCode={} toCurrencyCode={} fromCurrencyAmount={}", fromCurrencyCode, toCurrencyCode, fromCurrencyAmount);
        if (StringUtils.isBlank(fromCurrencyCode) || StringUtils.isBlank(toCurrencyCode) || fromCurrencyAmount == null) {
            log.error("输入参数错误");
            return null;
        }
        // 请求级别缓存
        String requestId = TraceContext.getRequestId();
        if (StringUtils.isBlank(requestId)) {
            log.error("请求级别缓存失败，requestId为空");
            return exchange(fromCurrencyAmount, getExchangeInfo(fromCurrencyCode, toCurrencyCode));
        }
        // 构造key
        StringBuilder key = new StringBuilder().append(requestId).append(fromCurrencyCode).append(toCurrencyCode);
        log.info("货币转换访问本地缓存 key={}", key);
        // 获取汇率
        ExchangeInfoBo exchangeInfoBo = EXCHANGE_TOKEN_CACHE.getIfPresent(key.toString());
        log.info("从缓存获取汇率 exchangeInfoBo={}", exchangeInfoBo);
        if (exchangeInfoBo == null) {
            exchangeInfoBo = getExchangeInfo(fromCurrencyCode, toCurrencyCode);
            log.info("远程调用获取汇率 exchangeInfoBo={}", exchangeInfoBo);
            if (exchangeInfoBo == null) {
                log.error("汇率信息获取失败 fromCurrencyCode={} toCurrencyCode={}", fromCurrencyCode, toCurrencyCode);
                return null;
            } else {
                // 塞入本地缓存
                EXCHANGE_TOKEN_CACHE.put(key.toString(), exchangeInfoBo);
                log.info("塞入本地缓存成功");
            }
        }
        return exchange(fromCurrencyAmount, exchangeInfoBo);
    }

    private static CurrencyExchangeBO convert(CurrencyExchangeRespDTO respDTO) {
        CurrencyExchangeBO currencyExchangeBO = new CurrencyExchangeBO();
        currencyExchangeBO.setToCurrencyAmount(respDTO.getToCurrencyAmount());
        currencyExchangeBO.setExchangeToken(respDTO.getExchangeToken());
        return currencyExchangeBO;
    }

    private static ExchangeTokenBO convert(ExchangeTokenRespDTO respDTO) {
        ExchangeTokenBO exchangeTokenBO = new ExchangeTokenBO();
        exchangeTokenBO.setAmountAdding(respDTO.getAmountAdding().stripTrailingZeros());
        exchangeTokenBO.setCarryRule(respDTO.getCarryRule());
        exchangeTokenBO.setExchangeRate(respDTO.getExchangeRate());
        exchangeTokenBO.setFromCurrencyCode(respDTO.getFromCurrencyCode());
        exchangeTokenBO.setToCurrencyCode(respDTO.getToCurrencyCode());
        return exchangeTokenBO;
    }

    private static ExchangeInfoBo convert(ExchangeTokenRespDTO exchangeTokenRespDTO, String exchangeToken) {
        ExchangeInfoBo exchangeInfoBo = new ExchangeInfoBo();
        exchangeInfoBo.setAmountAdding(exchangeTokenRespDTO.getAmountAdding().stripTrailingZeros());
        exchangeInfoBo.setCarryRule(exchangeTokenRespDTO.getCarryRule());
        exchangeInfoBo.setExchangeRate(exchangeTokenRespDTO.getExchangeRate());
        exchangeInfoBo.setExchangeToken(exchangeToken);
        exchangeInfoBo.setFromCurrencyCode(exchangeTokenRespDTO.getFromCurrencyCode());
        exchangeInfoBo.setToCurrencyCode(exchangeTokenRespDTO.getToCurrencyCode());
        return exchangeInfoBo;
    }

    private static ExchangeInfoBo getExchangeInfo(String fromCurrencyCode, String toCurrencyCode) {
        log.info("开始获取汇率 fromCurrencyCode={} toCurrencyCode={}", fromCurrencyCode, toCurrencyCode);
        BasicManageCurrencyClient currencyClient = SpringContext.getBean(BasicManageCurrencyClient.class);
        JSONResult<GetExchangeInfoRespVo> result = currencyClient.getExchangeInfo(fromCurrencyCode, toCurrencyCode);
        if(result == null || result.getData() == null) {
            log.error("获取汇率失败 result={}", result);
            return null;
        }
        GetExchangeInfoRespVo getExchangeInfoRespVo = result.getData();
        log.info("获取汇率成功 getExchangeInfoRespVo={}", getExchangeInfoRespVo);
        return convert(getExchangeInfoRespVo);
    }

    private static ExchangeInfoBo convert(GetExchangeInfoRespVo getExchangeInfoRespVo) {
        ExchangeInfoBo exchangeInfoBo = new ExchangeInfoBo();
        exchangeInfoBo.setAmountAdding(getExchangeInfoRespVo.getAmountAdding());
        exchangeInfoBo.setCarryRule(getExchangeInfoRespVo.getCarryRule());
        exchangeInfoBo.setExchangeRate(getExchangeInfoRespVo.getExchangeRate());
        exchangeInfoBo.setFromCurrencyCode(getExchangeInfoRespVo.getFromCurrencyCode());
        exchangeInfoBo.setToCurrencyCode(getExchangeInfoRespVo.getToCurrencyCode());
        exchangeInfoBo.setExchangeToken(getExchangeInfoRespVo.getExchangeToken());
        return exchangeInfoBo;
    }

    private static ExchangeInfoBo exchange(BigDecimal fromCurrencyAmount, ExchangeInfoBo exchangeInfoBo) {
        if (fromCurrencyAmount == null || exchangeInfoBo == null || exchangeInfoBo.getExchangeRate() == null || exchangeInfoBo.getAmountAdding() == null || exchangeInfoBo.getCarryRule() == null) {
            log.error("输入参数错误 fromCurrencyAmount={} exchangeInfoBo={}", fromCurrencyAmount, exchangeInfoBo);
            return null;
        }
        BigDecimal exchangeRate = exchangeInfoBo.getExchangeRate();
        BigDecimal amountAdding = exchangeInfoBo.getAmountAdding();
        amountAdding = amountAdding.stripTrailingZeros();
        exchangeInfoBo.setAmountAdding(amountAdding);
        Integer carryRule = convertRoundingMode(exchangeInfoBo.getCarryRule());
        BigDecimal toCurrencyAmount = fromCurrencyAmount.multiply(exchangeRate).divide(amountAdding, 0, carryRule).multiply(amountAdding);
        exchangeInfoBo.setFromCurrencyAmount(fromCurrencyAmount);
        exchangeInfoBo.setToCurrencyAmount(toCurrencyAmount);
        log.info("转换货币成功 exchangeInfoBo={}", exchangeInfoBo);
        return exchangeInfoBo;
    }

    private static Integer convertRoundingMode(Integer carryRule) {
        switch (carryRule) {
            case 0x0:
                return BigDecimal.ROUND_FLOOR;
            case 0x1:
                return BigDecimal.ROUND_CEILING;
            case 0x2:
                return BigDecimal.ROUND_UP;
            case 0x3:
                return BigDecimal.ROUND_DOWN;
            case 0x5:
                return BigDecimal.ROUND_HALF_UP;
            case 0x6:
                return BigDecimal.ROUND_HALF_EVEN;
            default:
                return BigDecimal.ROUND_HALF_UP;
        }
    }

}
